package com.issac.OnsiteSearchEngine.searcher;

import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;

import java.io.*;
import java.util.*;

public class DocSearcher {
    /**
     * DocSearcher类完成整个搜索过程
     */

    // 使用这个 HashSet 来保存停用词
    private HashSet<String> stopWords = new HashSet<>();

    // 此处要加上索引对象的实例
    // 同时要完成索引加载的工作.
    private Index index = new Index();

    public DocSearcher() {
        index.loadIndexToMemory();
        loadStopWords();
    }

//    public void loadStopWords() {
//        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(STOP_WORD_PATH))) {
//            while (true) {
//                String line = bufferedReader.readLine();
//                if (line == null) {
//                    // 读取文件完毕!
//                    break;
//                }
//                stopWords.add(line);
//            }
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }

    public void loadStopWords() {
        InputStream inputStream = getClass().getClassLoader().getResourceAsStream("stop_words.txt");
        if (inputStream == null) {
            System.err.println("停用词文件未找到！");
            return;
        }
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stopWords.add(line.trim());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public List<Result> search(String query) {
        // 1. 珍贵查询词query进行分词
        List<Term> termsBeforeFilter = ToAnalysis.parse(query).getTerms();
        List<Term> termsAfterFilter = new ArrayList<>();
        // 针对分词结果, 使用停用词（无意义的词：i、have、is......）表进行过滤
        for (Term term : termsBeforeFilter) {
            if (stopWords.contains(term.getName())) {
                continue;
            }
            termsAfterFilter.add(term);
        }

        // 2. 针对分词结果来查倒排
        List<List<Weight>> termResult = new ArrayList<>();
        for (Term term : termsAfterFilter) {
            String word = term.getName();
            // 虽然倒排索引中, 有很多的词. 但是这里的词一定都是之前的文档中存在的.
            List<Weight> invertedList = index.getDocsFromInvertedIndex(word);
            if (invertedList == null) {
                // 说明这个词在所有文档中都不存在.
                continue;
            }
            termResult.add(invertedList);
        }

        // 3. 【合并】针对多个分词结果触发出的相同文档, 进行权重合并
        List<Weight> allTermResult = mergeResult(termResult);

        // 4. 【排序】针对触发的结果按照权重降序排序
        allTermResult.sort(new Comparator<Weight>() {
            @Override
            public int compare(Weight o1, Weight o2) {
                // 如果是升序排序: return o1.getWeight() - o2.getWeight();
                // 如果是降序排序: return o2.getWeight() - o1.getWeight();
                return o2.getWeight() - o1.getWeight();
            }
        });

        // 5. 【包装结果】针对排序的结果, 去查正排, 构造出要返回的数据
        List<Result> results = new ArrayList<>();
        for (Weight weight : allTermResult) {
            DocInfo docInfo = index.getDocInfoByDocId(weight.getDocId());
            Result result = new Result();
            result.setTitle(docInfo.getTitle());
            result.setUrl(docInfo.getUrl());
            result.setDesc(GenDesc(docInfo.getContent(), termsAfterFilter));
            results.add(result);
        }
        return results;
    }

    static class Pos {
        /**
         * 内部类： 描述一个元素在二维数组中的位置
         */
        public int row;
        public int col;

        public Pos(int row, int col) {
            this.row = row;
            this.col = col;
        }
    }

    private List<Weight> mergeResult(List<List<Weight>> termResult) {
        // 1. 针对每一行进行排序(按照 id 进行升序排序)
        for (List<Weight> curRow : termResult) {
            curRow.sort(new Comparator<Weight>() {
                @Override
                public int compare(Weight o1, Weight o2) {
                    return o1.getDocId() - o2.getDocId();
                }
            });
        }

        // 2. 借助优先级队列, 针对这些行进行合并
        List<Weight> combinedResult = new ArrayList<>();

        // 1)  创建优先级队列, 并指定比较规则(按照 Weight 的 docId, 取小的更优先)
        PriorityQueue<Pos> queue = new PriorityQueue<>(new Comparator<Pos>() {
            @Override
            public int compare(Pos o1, Pos o2) {
                // 先根据 pos 值找到对应的 Weight 对象, 再根据 Weight 的 docId 来排序
                Weight w1 = termResult.get(o1.row).get(o1.col);
                Weight w2 = termResult.get(o2.row).get(o2.col);
                return w1.getDocId() - w2.getDocId();
            }
        });

        // 2） 初始化队列, 把每一行的第一个元素放到队列中
        for (int row = 0; row < termResult.size(); row++) {
            // 初始插入的元素的 col 就是 0
            queue.offer(new Pos(row, 0));
        }

        // 3） 循环取队首元素，即当前若干行中的min元素
        while (!queue.isEmpty()) {
            Pos minPos = queue.poll();
            Weight curWeight = termResult.get(minPos.row).get(minPos.col);
            //  4） 看看这个取到的 Weight 是否和前一个插入到 combinedResult 中的结果是相同的 docId
            //      如果是, 就合并
            if (combinedResult.size() > 0) {
                // 取出了上次插入的元素
                Weight lastWeight = combinedResult.get(combinedResult.size() - 1);
                if (lastWeight.getDocId() == curWeight.getDocId()) {
                    // 说明遇到了相同的文档.
                    // 合并权重
                    lastWeight.setWeight(lastWeight.getWeight() + curWeight.getWeight());
                } else {
                    // 如果文档 id 不相同, 就直接把 curWeight 给插入到 target 的末尾
                    combinedResult.add(curWeight);
                }
            } else {
                // combinedResult 当前是空着的, 就直接插入即可
                combinedResult.add(curWeight);
            }
            //  5） 把当前元素处理完了之后, 要把对应这个元素的光标往后移动, 去取这一行的下一个元素
            Pos newPos = new Pos(minPos.row, minPos.col + 1);
            if (newPos.col >= termResult.get(newPos.row).size()) {
                // 如果移动光标之后, 超出了这一行的列数, 就说明到达末尾了.
                // 到达末尾之后说明这一行就处理完毕
                continue;
            }
            queue.offer(newPos);
        }
        return combinedResult;
    }

    private String GenDesc(String content, List<Term> terms) {
        // 先遍历分词结果, 看看哪个结果是在 content 中存在
        int firstPos = -1;
        for (Term term : terms) {
            // 把正文也先转成小写, 然后再查询
            String word = term.getName().toLowerCase();
            // 此处需要的是 "全字匹配", 让 word 能够独立成词, 才要查找出来, 而不是只作为词的一部分
            content = content.toLowerCase().replaceAll("\\b" + word + "\\b", " " + word + " ");
            firstPos = content.indexOf(" " + word + " ");
            if (firstPos >= 0) {
                // 找到位置
                break;
            }
        }
        if (firstPos == -1) {
            // 所有的分词结果都不在正文中存在
            // 此时就直接返回一个空的描述了 or 直接取正文的前 160 个字符作为描述
            if (content.length() > 160) {
                return content.substring(0, 160) + "...";
            }
            return content;
        }
        // 从 firstPos 作为基准位置, 往前找 60 个字符, 作为描述的起始位置
        String desc = "";
        int descBeg = firstPos < 60 ? 0 : firstPos - 60;
        if (descBeg + 160 > content.length()) {
            desc = content.substring(descBeg);
        } else {
            desc = content.substring(descBeg, descBeg + 160) + "...";
        }

        // 在此处加上一个替换操作. 把描述中的和分词结果相同的部分, 给加上一层 <i> 标签. 就可以通过 replace 的方式来实现
        for (Term term : terms) {
            String word = term.getName();
            // 注意, 此处要进行全字匹配. 也就是当查询词为 List 的时候 不能把 ArrayList 中的 List 给单独标红
            desc = desc.replaceAll("(?i) " + word + " ", "<i> " + word + " </i>");
        }
        return desc;
    }

}
